#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include "aux_types.h"
#include "aux_errno.h"
#include "aux_socket.h"
#include "aux_debugmsg.h"
#include "aux_ip.h"

static EErrNo xIPCLinuxSocketPlatformInit()
{
#define __FUNCTION_NAME__ "xIPCLinuxSocketPlatformInit"
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo xIPCLinuxSocketInit(TxIPCSocket *ptSocket)
{
#define __FUNCTION_NAME__ "xIPCLinuxSocketInit"

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket, SOCKET_ACTION_INIT))
    {
        AUX_ERR_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }
    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_INITIALIZED);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo xIPCLinuxSocketCreate(TxIPCSocket *ptSocket,
                                            s32 nSendTimeout, s32 nRecvTimeout)
{
#define __FUNCTION_NAME__ "xIPCLinuxSocketCreate"

    struct timeval recvTimeout = {nRecvTimeout/1000, (nRecvTimeout%1000)*1000};
    struct timeval sendTimeout = {nSendTimeout/1000, (nSendTimeout%1000)*1000};

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket, SOCKET_ACTION_CREAT))
    {
        AUX_ERR_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }
    ptSocket->nSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (0 > ptSocket->nSocket)
    {
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CREATE_FAIL);
        return ERRNO_FAIL;
    }

    if (nRecvTimeout > 0)
        setsockopt(ptSocket->nSocket, SOL_SOCKET, SO_RCVTIMEO,
                    (char *)&recvTimeout ,sizeof(struct timeval));

    if (nSendTimeout > 0)
        setsockopt(ptSocket->nSocket, SOL_SOCKET, SO_SNDTIMEO,
                    (char *)&sendTimeout ,sizeof(struct timeval));

    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CREATED);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo xIPCLinuxSocketConnect(TxIPCSocket *ptSocket)
{
#define __FUNCTION_NAME__ "xIPCLinuxSocketConnect"

    struct sockaddr_in tAddrSrv;
    s32    nError = 0;

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket,
                                                SOCKET_ACTION_CLIENT_CONNECT) ||
        ERRNO_FALSE == aux_chk_ipv4_addr_str(ptSocket->szIpStr))
    {
        AUX_ERR_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }
    if (ptSocket->wPort < 1024)
        aux_prt_warn("port %d < 1024, sth maybe wrong\n", ptSocket->wPort);

    memset((void *)&tAddrSrv, 0, sizeof(struct sockaddr_in));
    if(inet_pton(AF_INET, ptSocket->szIpStr, &tAddrSrv.sin_addr) <= 0)
    {
        return -ERRNO_EPARAMETER_INVALID;
    }
    tAddrSrv.sin_family = AF_INET;
    tAddrSrv.sin_port = htons(ptSocket->wPort);
    nError = connect(ptSocket->nSocket,  (struct sockaddr*)&tAddrSrv,
                     sizeof(struct sockaddr_in));
    if (nError < 0)
    {
        /* connect fail */
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLIENT_CONNECT_FAIL);
        aux_prt_err("connect ret %d, errno %d %s\n", \
                    nError, errno, strerror(errno));
        return -ERR(errno);
    }

    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLIENT_CONNECT);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo xIPCLinuxSocketSend(TxIPCSocket *ptSocket,
                                          u8 *pabyData, s32 *pnSize)
{
#define __FUNCTION_NAME__ "xIPCLinuxSocketSend"

    s32 nSizeGotoSend = 0;

    if (ERRNO_SUCCESS !=
                    xIPCCommonChkSocketAction(ptSocket, SOCKET_ACTION_SEND) ||
        !pabyData || !pnSize)
    {
        AUX_ERR_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    if (0 == *pnSize)
    {
        goto EXIT;
    }

    nSizeGotoSend = *pnSize;

    *pnSize = send(ptSocket->nSocket, pabyData, nSizeGotoSend, MSG_NOSIGNAL);

    /* if send fail */
    if (0 > *pnSize)
    {
        s32 nErrorNo = errno;
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_SEND_FAIL);
        aux_prt_err("send ret=%d, errno %d %s\n", \
                    *pnSize, errno, strerror(nErrorNo));
        *pnSize = 0;
        if (EPIPE == nErrorNo)
            return ERRNO_REMOTE_CLOSED;
        else
            return -ERR(nErrorNo);
    }

    /* maybe, not send all the data, but we place the size sended in *pnSize */

EXIT:
    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_SEND);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo xIPCLinuxSocketRecv(TxIPCSocket *ptSocket,
                                          u8 *pabyData, s32 *pnSize)
{
#define __FUNCTION_NAME__ "xIPCLinuxSocketRecv"

    s32 nSizeGotoRecv = 0;

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket, SOCKET_ACTION_RECV))
    {
        AUX_ERR_STR("check action fail\n");
        AUX_DEBUG_STR_INT("current state", \
                    (xIPCGetSocketState(ptSocket, SOCKET_STATE_INDEX_CUR)));
        AUX_DEBUG_RAW_STR("\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    if (!pabyData || !pnSize)
    {
        AUX_ERR_STR("parameter error\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    if (0 == *pnSize)
        goto EXIT;

    nSizeGotoRecv = *pnSize;

    *pnSize = recv(ptSocket->nSocket, pabyData, nSizeGotoRecv, MSG_NOSIGNAL);
    // if socket has been closed, the return value is 0, or the byte cnt recved
    if (0 > *pnSize)
    {
        int nError = errno;
        if (EINTR == nError || EWOULDBLOCK == nError || EAGAIN == nError)
        {
            return ERRNO_TIMEOUT;
        }
        else
        {
            AUX_DEBUG_STR("remote close socket\n");
            close(ptSocket->nSocket);
            xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLOSE);
            return ERRNO_REMOTE_CLOSED;
        }
        /*
        else
        {
            xIPCPushCurSocketState(ptSocket, SOCKET_STATE_RECV_FAIL);
            AUX_DEBUG_STR_INT("recv err =", nError);
            AUX_DEBUG_RAW_STR("\n");
            return -ERR(nError);
        }
        */
    }

    if (0 == *pnSize) /* the remote has closed */
    {
        close(ptSocket->nSocket);
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLOSE);
        return ERRNO_REMOTE_CLOSED;
    }
EXIT:
    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_RECV);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo xIPCLinuxSocketClose(TxIPCSocket *ptSocket)
{
#define __FUNCTION_NAME__ "xIPCLinuxSocketClose"

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket, SOCKET_ACTION_CLOSE))
    {
        AUX_ERR_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    if (close(ptSocket->nSocket))
    {
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLOSE_FAIL);
        return -ERR(errno);
    }

    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLOSE);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo xIPCLinuxSocketDeinit(TxIPCSocket *ptSocket)
{
#define __FUNCTION_NAME__ "xIPCLinuxSocketDeinit"

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket,
                                                   SOCKET_ACTION_DEINIT))
    {
        AUX_ERR_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }
    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_DEINITIALIZED);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo xIPCLinuxSocketPlatformDeinit()
{
#define __FUNCTION_NAME__ "xIPCLinuxSocketPlatformDeinit"
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo xIPCLinuxSocketShutdown(TxIPCSocket *ptSocket, u8 byRx, u8 byTx)
{
#define __FUNCTION_NAME__ "xIPCLinuxSocketShutdown"
    int nHow;

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket, SOCKET_ACTION_CLOSE))
    {
        AUX_ERR_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    if (!byRx && !byTx)
        return ERRNO_SUCCESS;

    if (byRx)
    {
        if (byTx)
            nHow = SHUT_RDWR;
        else
            nHow = SHUT_RD;
    }
    else
    {
        if (byTx)
            nHow = SHUT_WR;
    }
    if (shutdown(ptSocket->nSocket, nHow))
    {
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLOSE_FAIL);
        return -ERR(errno);
    }

    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLOSE);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__
}


/////////////////////////////////////////////////////////////////
////////////////////Server Specific//////////////////////////////
static EErrNo xIPCLinuxSocketBind(TxIPCSocket *ptSocket)
{
#define __FUNCTION_NAME__ "xIPCLinuxSocketBind"

    struct sockaddr_in tAddrSrv;

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket,
                                                   SOCKET_ACTION_SERVER_BIND) ||
        ERRNO_FALSE == aux_chk_ipv4_addr_str(ptSocket->szIpStr))
    {
        AUX_ERR_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    if (ptSocket->wPort < 1024)
        aux_prt_warn("port %d < 1024, sth maybe wrong\n", ptSocket->wPort);

    memset((void *)&tAddrSrv, 0, sizeof(struct sockaddr_in));

    if(inet_pton(AF_INET, ptSocket->szIpStr, &tAddrSrv.sin_addr) <= 0)
    {
        return -ERRNO_EPARAMETER_INVALID;
    }

    tAddrSrv.sin_family           = AF_INET;
    tAddrSrv.sin_port             = htons(ptSocket->wPort);
    if (bind(ptSocket->nSocket, (struct sockaddr*)&tAddrSrv, sizeof(struct sockaddr_in)) < 0)
    {
        /* bind fail */
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_SERVER_BIND_FAIL);
        return -ERR(errno);
    }

    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_SERVER_BIND);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo xIPCLinuxSocketListen(TxIPCSocket *ptSocket)
{
#define __FUNCTION_NAME__ "xIPCLinuxSocketListen"

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket,
                                                   SOCKET_ACTION_SERVER_LISTEN))
    {
        AUX_ERR_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    if (listen(ptSocket->nSocket, SOMAXCONN) < 0)
    {
        AUX_ERR_STR("listen fail\n");
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_SERVER_LISTEN_FAIL);
        return -ERR(errno);
    }

    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_SERVER_LISTEN);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__
}

static EErrNo xIPCLinuxSocketReset(TxIPCSocket *ptSocket)
{
#define __FUNCTION_NAME__ "xIPCLinuxSocketReset"
    xIPCLinuxSocketClose(ptSocket);
    xIPCLinuxSocketDeinit(ptSocket);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo xIPCLinuxSocketAccept(TxIPCSocket *ptLocal, TxIPCSocket *ptRemote)
{
#define __FUNCTION_NAME__ "xIPCLinuxSocketAccept"
    struct sockaddr_in addrRemote;
    s32 nRet = 0;
    int nLen = sizeof(addrRemote);

    if (!ptLocal || !ptRemote ||
        ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptLocal,
                                                   SOCKET_ACTION_SERVER_ACCEPT))
    {
        AUX_ERR_STR("check action fail\n");
        AUX_DEBUG_STR_INT("current state", \
                    (xIPCGetSocketState(ptLocal, SOCKET_STATE_INDEX_CUR)));
        AUX_DEBUG_RAW_STR("\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    memset(ptRemote, 0, sizeof(TxIPCSocket));
    memset(&addrRemote, 0, sizeof(struct sockaddr_in));
    nRet = accept(ptLocal->nSocket,
                  (struct sockaddr *)&addrRemote,
                  &nLen);
    if (0 > nRet)
    {
        xIPCPushCurSocketState(ptLocal, SOCKET_STATE_SERVER_ACCEPT_FAIL);
        if (EAGAIN == errno)
        {
            return ERRNO_TIMEOUT;
        }
        /* accept has no timeout machnism */
        aux_prt_err("accept fail, errno %d %s\n", errno, strerror(errno));
        return -ERR(errno);
    }

    ptRemote->nSocket = nRet;

    if (ERRNO_FAIL == aux_ipv4_num2str(addrRemote.sin_addr.s_addr,
                                          &(ptRemote->szIpStr[0]),
                                          sizeof((ptRemote->szIpStr))))
    {
        AUX_ERR_STR("aux_ipv4_num2str fail\n");
        memset(&(ptRemote->szIpStr[0]), 0, sizeof((ptRemote->szIpStr)));
    }

    xIPCPushCurSocketState(ptLocal, SOCKET_STATE_SERVER_ACCEPT);
    xIPCPushCurSocketState(ptRemote, SOCKET_STATE_RECV);
    ptRemote->eType = SOCKET_TYPE_REMOTE_CLIENT;

    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__
}

EErrNo xIPCLinuxSocketInterface(TxIPCSocketInterface *pInterface)
{
    if (!pInterface)
        return -ERRNO_EPARAMETER_INVALID;

    memset(pInterface, 0, sizeof(TxIPCSocketInterface));

    pInterface->Init    = xIPCLinuxSocketInit;
    pInterface->Create  = xIPCLinuxSocketCreate;
    pInterface->Connect = xIPCLinuxSocketConnect;
    pInterface->Send    = xIPCLinuxSocketSend;
    pInterface->Recv    = xIPCLinuxSocketRecv;
    pInterface->Close   = xIPCLinuxSocketClose;
    pInterface->Deinit  = xIPCLinuxSocketDeinit;
    pInterface->Bind    = xIPCLinuxSocketBind;
    pInterface->Listen  = xIPCLinuxSocketListen;
    pInterface->Accept  = xIPCLinuxSocketAccept;
    pInterface->Shutdown= xIPCLinuxSocketShutdown;
    pInterface->Reset          = xIPCLinuxSocketReset;
    pInterface->PlatformDeinit = xIPCLinuxSocketPlatformDeinit;
    pInterface->PlatformInit   = xIPCLinuxSocketPlatformInit;

    return ERRNO_SUCCESS;
}

